﻿//SmartComWrapper.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Smart
{
	using System;
	using System.Net;
	using System.Security;
	using System.Threading;

	using Ecng.Collections;
	using Ecng.Common;
	using Ecng.ComponentModel;
	using Ecng.Interop;
	using Ecng.Net;

	using StockSharp.BusinessEntities;

	using StClientLib;

	/// <summary>
	/// Обертка над SmartCOM API.
	/// </summary>
	public class SmartComWrapper : Disposable
	{
		private enum Trying
		{
			None,
			Connect,
			Disconnect,
		}

		private readonly SynchronizedSet<string> _registeredPortfolios = new SynchronizedSet<string>();
		private readonly Action<Action> _process;
		private StServer _server;
		private Timer _checkAliveTimer;
		private DateTime _lastTransactionTime;
		private Trying _trying;
		private bool? _errorWhileDisconnect;

		private readonly EventDispatcher _eventDispatcher;

		/// <summary>
		/// Создать <see cref="SmartComWrapper"/>.
		/// </summary>
		/// <param name="process">Обработчик для распараллеливания событий.</param>
		/// <param name="errorHandler">Обработчик ошибок.</param>
		public SmartComWrapper(Action<Action> process, Action<Exception> errorHandler)
		{
			if (process == null)
				throw new ArgumentNullException("process");

			if (errorHandler == null)
				throw new ArgumentNullException("errorHandler");

			_process = process;
			_eventDispatcher = new EventDispatcher(errorHandler);
		}

		/// <summary>
		/// Доступно ли соединение.
		/// </summary>
		public bool IsConnected { get; private set; }

		private SecureString _login;

		/// <summary>
		/// Логин.
		/// </summary>
		public string Login
		{
			get { return _login.To<string>(); }
			set
			{
				if (value.IsEmpty())
					throw new ArgumentNullException("value");

				if (IsConnected)
					throw new InvalidOperationException("Перед сменой логина на новый необходимо отключиться.");

				_login = value.To<SecureString>();
			}
		}

		private SecureString _password;

		/// <summary>
		/// Пароль.
		/// </summary>
		public string Password
		{
			get { return _password.To<string>(); }
			set
			{
				if (value.IsEmpty())
					throw new ArgumentNullException("value");

				if (IsConnected)
					throw new InvalidOperationException("Перед сменой пароля на новый необходимо отключиться.");

				_password = value.To<SecureString>();
			}
		}

		private EndPoint _address;

		/// <summary>
		/// Адрес сервера. Значение по-умолчанию равно  <see cref="SmartAddresses.Major"/>.
		/// </summary>
		public EndPoint Address
		{
			get { return _address; }
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				if (IsConnected)
					throw new InvalidOperationException("Перед сменой адреса сервера на новый необходимо отключиться от текущего.");

				_address = value;
			}
		}

		/// <summary>
		/// Событие о появлении инструмента.
		/// </summary>
		/// <remarks>
		/// Передаваемые параметры:
		/// <list type="number">
		/// <item>
		/// <description>Номер инструмента в справочнике.</description>
		/// </item>
		/// <item>
		/// <description>Всего инструментов в справочнике.</description>
		/// </item>
		/// <item>
		/// <description>Код ЦБ из таблицы котировок SmartTrade.</description>
		/// </item>
		/// <item>
		/// <description>Краткое наименование.</description>
		/// </item>
		/// <item>
		/// <description>Полное наименование.</description>
		/// </item>
		/// <item>
		/// <description>Код типа из справочника SmartTrade.</description>
		/// </item>
		/// <item>
		/// <description>Точность цены.</description>
		/// </item>
		/// <item>
		/// <description>Размер лота ценных бумаг.</description>
		/// </item>
		/// <item>
		/// <description>Цена минимального шага.</description>
		/// </item>
		/// <item>
		/// <description>Минимальный шаг цены.</description>
		/// </item>
		/// <item>
		/// <description>ISIN.</description>
		/// </item>
		/// <item>
		/// <description>Наименование площадки.</description>
		/// </item>
		/// <item>
		/// <description>Дата экспирации.</description>
		/// </item>
		/// <item>
		/// <description>Дней до экспирации.</description>
		/// </item>
		/// <item>
		/// <description>Страйк.</description>
		/// </item>
		/// </list>
		/// </remarks>
		public event Action<int, int, string, string, string, string, int, int, decimal, decimal, string, string, DateTime?, decimal, decimal> NewSecurity;

		/// <summary>
		/// Событие об изменении инструмента.
		/// </summary>
		/// <remarks>
		/// Передаваемые параметры:
		/// <list type="number">
		/// <item>
		/// <description>Код ЦБ из таблицы котировок SmartTrade.</description>
		/// </item>
		/// <item>
		/// <description>Информация о последней сделке.</description>
		/// </item>
		/// <item>
		/// <description>Цена первой сделки в текущей сессии.</description>
		/// </item>
		/// <item>
		/// <description>Максимальная цена сделки в текущей сессии.</description>
		/// </item>
		/// <item>
		/// <description>Минимальная цена сделки в текущей сессии.</description>
		/// </item>
		/// <item>
		/// <description>Цена последней сделки предыдущей торговой сессии.</description>
		/// </item>
		/// <item>
		/// <description>Объём по ЦБ за текущую торговую сессию.</description>
		/// </item>
		/// <item>
		/// <description>Информация о спросе.</description>
		/// </item>
		/// <item>
		/// <description>Информация о предложении.</description>
		/// </item>
		/// <item>
		/// <description>Открытые позиции.</description>
		/// </item>
		/// <item>
		/// <description>Гарантийное обеспечение (фьючерсы).</description>
		/// </item>
		/// <item>
		/// <description>Гарантийное обеспечение продажи опционов и по синтетическим позициям (опционы).</description>
		/// </item>
		/// <item>
		/// <description>Лимит цены.</description>
		/// </item>
		/// <item>
		/// <description>Статус.</description>
		/// </item>
		/// <item>
		/// <description>Волатильность и теоретическая цена.</description>
		/// </item>
		/// </list>
		/// </remarks>
		public event Action<string, Trade, decimal, decimal, decimal, decimal, decimal, Quote, Quote, decimal, Tuple<decimal, decimal>, Tuple<decimal, decimal>, Tuple<decimal, decimal>, int, Tuple<decimal, decimal>> SecurityChanged;

		/// <summary>
		/// Событие о появлении портфеля.
		/// </summary>
		/// <remarks>
		/// Передаваемые параметры:
		/// <list type="number">
		/// <item>
		/// <description>Номер счета в списке.</description>
		/// </item>
		/// <item>
		/// <description>Всего счетов в списке.</description>
		/// </item>
		/// <item>
		/// <description>Наименование портфеля.</description>
		/// </item>
		/// <item>
		/// <description>Площадка доступная для портфеля.</description>
		/// </item>
		/// <item>
		/// <description>Статус портфеля.</description>
		/// </item>
		/// </list>
		/// </remarks>
		[CLSCompliant(false)]
		public event Action<int, int, string, string, StPortfolioStatus> NewPortfolio;

		/// <summary>
		/// Событие об изменении портфеля.
		/// </summary>
		/// <remarks>
		/// Передаваемые параметры:
		/// <list type="number">
		/// <item>
		/// <description>Номер торгового счёта на торговой площадке.</description>
		/// </item>
		/// <item>
		/// <description>Сумма доступных наличных средств на счёте.</description>
		/// </item>
		/// <item>
		/// <description>Плечо для маржинальной торговли.</description>
		/// </item>
		/// <item>
		/// <description>Сумма биржевой комиссии за торговый день.</description>
		/// </item>
		/// <item>
		/// <description>Сальдо торгового дня.</description>
		/// </item>
		/// </list>
		/// </remarks>
		public event Action<string, decimal, decimal, decimal, decimal> PortfolioChanged;

		/// <summary>
		/// Событие об изменении позиции.
		/// </summary>
		/// <remarks>
		/// Передаваемые параметры:
		/// <list type="number">
		/// <item>
		/// <description>Номер торгового счёта на торговой площадке.</description>
		/// </item>
		/// <item>
		/// <description>Код ЦБ из таблицы котировок SmartTrade.</description>
		/// </item>
		/// <item>
		/// <description>Средневзвешенная цена.</description>
		/// </item>
		/// <item>
		/// <description>Объём сделки, если положительный Long, отрицательный в случае Short.</description>
		/// </item>
		/// <item>
		/// <description>Количество ЦБ с учетом выставленных заявок.</description>
		/// </item>
		/// </list>
		/// </remarks>
		public event Action<string, string, decimal, decimal, decimal> PositionChanged;

		/// <summary>
		/// Событие о появлении собственной сделки.
		/// </summary>
		/// <remarks>
		/// Передаваемые параметры:
		/// <list type="number">
		/// <item>
		/// <description>Номер торгового счёта на торговой площадке.</description>
		/// </item>
		/// <item>
		/// <description>Код ЦБ из таблицы котировок SmartTrade.</description>
		/// </item>
		/// <item>
		/// <description>Номер заявки на сервере котировок.</description>
		/// </item>
		/// <item>
		/// <description>Цена сделки.</description>
		/// </item>
		/// <item>
		/// <description>Объём сделки.</description>
		/// </item>
		/// <item>
		/// <description>Время сделки.</description>
		/// </item>
		/// <item>
		/// <description>Id сделки на рынке.</description>
		/// </item>
		/// </list>
		/// </remarks>
		public event Action<string, string, long, decimal, decimal, DateTime, long> NewMyTrade;

		/// <summary>
		/// Событие о появлении тиковой сделки.
		/// </summary>
		/// <remarks>
		/// Передаваемые параметры:
		/// <list type="number">
		/// <item>
		/// <description>Код ЦБ из таблицы котировок SmartTrade.</description>
		/// </item>
		/// <item>
		/// <description>Время сделки.</description>
		/// </item>
		/// <item>
		/// <description>Цена сделки.</description>
		/// </item>
		/// <item>
		/// <description>Объём сделки.</description>
		/// </item>
		/// <item>
		/// <description>Id сделки на рынке.</description>
		/// </item>
		/// <item>
		/// <description>Направление сделки.</description>
		/// </item>
		/// </list>
		/// </remarks>
		[CLSCompliant(false)]
		public event Action<string, DateTime, decimal, decimal, long, StOrder_Action> NewTrade;

		/// <summary>
		/// Событие о появлении исторической тиковой сделки.
		/// </summary>
		/// <remarks>
		/// Передаваемые параметры:
		/// <list type="number">
		/// <item>
		/// <description>Номер сделки в списке.</description>
		/// </item>
		/// <item>
		/// <description>Всего сделок в списке.</description>
		/// </item>
		/// <item>
		/// <description>Код ЦБ из таблицы котировок SmartTrade.</description>
		/// </item>
		/// <item>
		/// <description>Время сделки.</description>
		/// </item>
		/// <item>
		/// <description>Цена сделки.</description>
		/// </item>
		/// <item>
		/// <description>Объём сделки.</description>
		/// </item>
		/// <item>
		/// <description>Id сделки на рынке.</description>
		/// </item>
		/// <item>
		/// <description>Направление сделки.</description>
		/// </item>
		/// </list>
		/// </remarks>
		[CLSCompliant(false)]
		public event Action<int, int, string, DateTime, decimal, decimal, long, StOrder_Action> NewHistoryTrade;

		/// <summary>
		/// Событие о появлении исторической временной свечки.
		/// </summary>
		/// <remarks>
		/// Передаваемые параметры:
		/// <list type="number">
		/// <item>
		/// <description>Номер бара в списке.</description>
		/// </item>
		/// <item>
		/// <description>Всего баров в списке.</description>
		/// </item>
		/// <item>
		/// <description>Код ЦБ из таблицы котировок SmartTrade.</description>
		/// </item>
		/// <item>
		/// <description>Интервал времени.</description>
		/// </item>
		/// <item>
		/// <description>Дата и время интервала.</description>
		/// </item>
		/// <item>
		/// <description>Цена первой сделки после открытия в интервале.</description>
		/// </item>
		/// <item>
		/// <description>Максимальная цена сделки в интервале.</description>
		/// </item>
		/// <item>
		/// <description>Минимальная цена сделки в интервале.</description>
		/// </item>
		/// <item>
		/// <description>Цена последней сделки в интервале.</description>
		/// </item>
		/// <item>
		/// <description>Объём сделок в интервале.</description>
		/// </item>
		/// <item>
		/// <description>Открытые позиции.</description>
		/// </item>
		/// </list>
		/// </remarks>
		public event Action<int, int, string, SmartTimeFrames, DateTime, decimal, decimal, decimal, decimal, decimal, decimal> NewBar;

		/// <summary>
		/// Событие о появлении новой заявки.
		/// </summary>
		/// <remarks>
		/// Передаваемые параметры:
		/// <list type="number">
		/// <item>
		/// <description>Уникальный номер заявки.</description>
		/// </item>
		/// <item>
		/// <description>Id заявки на сервере котировок.</description>
		/// </item>
		/// </list>
		/// </remarks>
		public event Action<int, string> NewOrder;

		/// <summary>
		/// Событие об изменении заявки.
		/// </summary>
		/// <remarks>
		/// Передаваемые параметры:
		/// <list type="number">
		/// <item>
		/// <description>Номер торгового счёта на торговой площадке.</description>
		/// </item>
		/// <item>
		/// <description>Код ЦБ из таблицы котировок SmartTrade.</description>
		/// </item>
		/// <item>
		/// <description>Состояние заявки.</description>
		/// </item>
		/// <item>
		/// <description>Вид торговой операции.</description>
		/// </item>
		/// <item>
		/// <description>Тип заявки.</description>
		/// </item>
		/// <item>
		/// <description>Срок действия приказа. True - один день, false - до отмены.</description>
		/// </item>
		/// <item>
		/// <description>Цена Лимит для заявок типа Лимит и Стоп-Лимит.</description>
		/// </item>
		/// <item>
		/// <description>Объем заявки.</description>
		/// </item>
		/// <item>
		/// <description>Цена СТОП для заявок типа Стоп и Стоп-Лимит.</description>
		/// </item>
		/// <item>
		/// <description>Объем, оставшийся в заявке.</description>
		/// </item>
		/// <item>
		/// <description>Время последнего изменения заявки.</description>
		/// </item>
		/// <item>
		/// <description>Id приказа на сервере котировок.</description>
		/// </item>
		/// <item>
		/// <description>Номер приказа на сервере котировок.</description>
		/// </item>
		/// <item>
		/// <description>Системный статус.</description>
		/// </item>
		/// <item>
		/// <description>Номер транзакции.</description>
		/// </item>
		/// </list>
		/// </remarks>
		[CLSCompliant(false)]
		public event Action<string, string, StOrder_State, StOrder_Action, StOrder_Type, bool, decimal, int, decimal, int, DateTime, string, long, int, int> OrderChanged;
		
		/// <summary>
		/// Событие об ошибке при регистрации заявки.
		/// </summary>
		/// <remarks>
		/// Передаваемые параметры:
		/// <list type="number">
		/// <item>
		/// <description>Уникальный номер заявки.</description>
		/// </item>
		/// <item>
		/// <description>Id заявки на сервере котировок.</description>
		/// </item>
		/// <item>
		/// <description>Причина.</description>
		/// </item>
		/// </list>
		/// </remarks>
		public event Action<int, string, string> OrderFailed;
		
		/// <summary>
		/// Событие об успешной перерегистрации заявки.
		/// </summary>
		/// <remarks>
		/// Передаваемые параметры:
		/// <list type="number">
		/// <item>
		/// <description>Id заявки на сервере котировок.</description>
		/// </item>
		/// </list>
		/// </remarks>
		public event Action<string> OrderReRegistered;
		
		/// <summary>
		/// Событие об ошибке при перерегистрации заявки.
		/// </summary>
		/// <remarks>
		/// Передаваемые параметры:
		/// <list type="number">
		/// <item>
		/// <description>Id заявки на сервере котировок.</description>
		/// </item>
		/// </list>
		/// </remarks>
		public event Action<string> OrderReRegisterFailed;
		
		/// <summary>
		/// Событие об успешной отмене заявки.
		/// </summary>
		/// <remarks>
		/// Передаваемые параметры:
		/// <list type="number">
		/// <item>
		/// <description>Id заявки на сервере котировок.</description>
		/// </item>
		/// </list>
		/// </remarks>
		public event Action<string> OrderCancelled;
		
		/// <summary>
		/// Событие об ошибке при отмене заявки.
		/// </summary>
		/// <remarks>
		/// Передаваемые параметры:
		/// <list type="number">
		/// <item>
		/// <description>Id заявки на сервере котировок.</description>
		/// </item>
		/// </list>
		/// </remarks>
		public event Action<string> OrderCancelFailed;
		
		/// <summary>
		/// Событие изменения стакана.
		/// </summary>
		/// <remarks>
		/// Передаваемые параметры:
		/// <list type="number">
		/// <item>
		/// <description>Код ЦБ из таблицы котировок SmartTrade.</description>
		/// </item>
		/// <item>
		/// <description>Порядковый номер строки в очереди заявок.</description>
		/// </item>
		/// <item>
		/// <description>Общее количество строк в очереди заявок.</description>
		/// </item>
		/// <item>
		/// <description>Цена на покупку.</description>
		/// </item>
		/// <item>
		/// <description>Объем ценных бумаг по цене на покупку.</description>
		/// </item>
		/// <item>
		/// <description>Цена на продажу.</description>
		/// </item>
		/// <item>
		/// <description>Объем ценных бумаг по цене на продажу.</description>
		/// </item>
		/// </list>
		/// </remarks>
		public event Action<string, int, int, decimal, decimal, decimal, decimal> QuoteChanged;

		/// <summary>
		/// Событие об успешном подсоединении к серверу SmartCOM.
		/// </summary>
		public event Action Connected;

		/// <summary>
		/// Событие об успешном отсоединении от сервера SmartCOM или о разрыве соединения.
		/// </summary>
		/// <remarks>
		/// Передаваемые параметры:
		/// <list type="number">
		/// <item>
		/// <description>Причина.</description>
		/// </item>
		/// </list>
		/// </remarks>
		public event Action<Exception> Disconnected;

		//[DllImport("ole32.dll")]
		//private static extern void CoFreeUnusedLibraries();

		/// <summary>
		/// Подключиться к SmartCOM.
		/// </summary>
		public void Connect()
		{
			if (Address == null)
				throw new InvalidOperationException("Адрес сервера SmartCOM не указан.");

			if (Login == null)
				throw new InvalidOperationException("Логин не указан.");

			if (Password == null)
				throw new InvalidOperationException("Пароль не указан.");

			_eventDispatcher.Add(() =>
			{
				_trying = Trying.Connect;
				_server = new StServerClass();
				SubscribeEvents();

				_server.connect(Address.GetHost(), (short)Address.GetPort(), Login, Password);
			});
		}

		/// <summary>
		/// Отключиться от SmartCOM.
		/// </summary>
		public void Disconnect()
		{
			CloseCheckAliveTimer();

			Do(server =>
			{
				if (_server == null)
				{
					RaiseDisconnected("Соединение не было установлено.");
					return;
				}

				_errorWhileDisconnect = null;
				_trying = Trying.Disconnect;

				try
				{
					server.disconnect();
				}
				finally
				{
					FreeSmartCom();
					//CoFreeUnusedLibraries();
				}

				if (_errorWhileDisconnect == null)
					RaiseDisconnected((Exception)null);
			});
		}

		/// <summary>
		/// Запустить экспорт данных.
		/// </summary>
		public void StartExport()
		{
			Do(server =>
			{
				server.GetSymbols();
				server.GetPrortfolioList();
			});
		}

		/// <summary>
		/// Зарегистрировать заявку.
		/// </summary>
		/// <param name="portfolioName">Номер портфеля.</param>
		/// <param name="securityId">Идентификатор инструмента.</param>
		/// <param name="action">Направление действия.</param>
		/// <param name="type">Тип заявки.</param>
		/// <param name="isOneDay">Время действия.</param>
		/// <param name="price">Цена.</param>
		/// <param name="volume">Объем.</param>
		/// <param name="stopPrice">Стоп цена (если регистрируется стоп-заявка).</param>
		/// <param name="transactionId">Уникальный номер транзакции.</param>
		[CLSCompliant(false)]
		public void RegisterOrder(string portfolioName, string securityId, StOrder_Action action, StOrder_Type type, bool isOneDay, double price, int volume, double stopPrice, int transactionId)
		{
			Do(server => server.PlaceOrder(portfolioName, securityId, action, type,
				isOneDay ? StOrder_Validity.StOrder_Validity_Day : StOrder_Validity.StOrder_Validity_Gtc,
				price, volume, stopPrice, transactionId));
		}

		/// <summary>
		/// Перерегистрировать заявку.
		/// </summary>
		/// <param name="portfolioName">Номер портфеля.</param>
		/// <param name="newPrice">Новая цена.</param>
		/// <param name="smartId">SmartCOM идентификатор заявки.</param>
		public void ReRegisterOrder(string portfolioName, double newPrice, string smartId)
		{
			if (portfolioName.IsEmpty())
				throw new ArgumentNullException("portfolioName");

			Do(server => server.MoveOrder(portfolioName, smartId, newPrice));
		}

		/// <summary>
		/// Отменить заявку.
		/// </summary>
		/// <param name="portfolioName">Номер портфеля.</param>
		/// <param name="securityId">Идентификатор инструмента.</param>
		/// <param name="smartId">SmartCOM идентификатор заявки.</param>
		public void CancelOrder(string portfolioName, string securityId, string smartId)
		{
			if (portfolioName.IsEmpty())
				throw new ArgumentNullException("portfolioName");

			if (securityId.IsEmpty())
				throw new ArgumentNullException("securityId");

			if (smartId.IsEmpty())
				throw new ArgumentNullException("smartId");

			Do(server => server.CancelOrder(portfolioName, securityId, smartId));
		}

		/// <summary>
		/// Отменить все активные заявки.
		/// </summary>
		public void CancelAllOrders()
		{
			Do(server => server.CancelAllOrders());
		}

		/// <summary>
		/// Начать получать новую информацию (например, <see cref="Security.LastTrade"/> или <see cref="Security.BestBid"/>) по инструменту.
		/// </summary>
		/// <param name="securityId">Идентификатор инструмента, по которому необходимо начать получать новую информацию.</param>
		public void RegisterSecurity(string securityId)
		{
			if (securityId.IsEmpty())
				throw new ArgumentNullException("securityId");

			Do(server => server.ListenQuotes(securityId));
		}

		/// <summary>
		/// Остановить получение новой информации.
		/// </summary>
		/// <param name="securityId">Идентификатор инструмента, по которому необходимо остановить получение новой информации.</param>
		public void UnRegisterSecurity(string securityId)
		{
			if (securityId.IsEmpty())
				throw new ArgumentNullException("securityId");

			if (!IsConnected)
				return;

			Do(server => server.CancelQuotes(securityId));
		}

		/// <summary>
		/// Начать получать котировки (стакан) по инструменту.
		/// Значение котировок можно получить через событие <see cref="QuoteChanged"/>.
		/// </summary>
		/// <param name="securityId">Идентификатор инструмента, по которому необходимо начать получать котировки.</param>
		public void RegisterQuotes(string securityId)
		{
			if (securityId.IsEmpty())
				throw new ArgumentNullException("securityId");

			Do(server => server.ListenBidAsks(securityId));
		}

		/// <summary>
		/// Остановить получение котировок по инструменту.
		/// </summary>
		/// <param name="securityId">Идентификатор инструмента, по которому необходимо остановить получение котировок.</param>
		public void UnRegisterQuotes(string securityId)
		{
			if (securityId.IsEmpty())
				throw new ArgumentNullException("securityId");

			if (!IsConnected)
				return;

			Do(server => server.CancelBidAsks(securityId));
		}

		/// <summary>
		/// Начать получать сделки (тиковые данные) по инструменту. Новые сделки будут приходить через событие <see cref="NewTrade"/>.
		/// </summary>
		/// <param name="securityId">Идентификатор инструмента, по которому необходимо начать получать сделки.</param>
		public void RegisterTrades(string securityId)
		{
			if (securityId.IsEmpty())
				throw new ArgumentNullException("securityId");

			// http://www.itinvest.ru/forum/index.php?showtopic=63071
			//_server.GetBars(security.Id, StBarInterval.StBarInterval_Tick, DateTime.MinValue, int.MaxValue);
			Do(server => server.ListenTicks(securityId));
		}

		/// <summary>
		/// Остановить получение сделок (тиковые данные) по инструменту.
		/// </summary>
		/// <param name="securityId">Идентификатор инструмента, по которому необходимо остановить получение сделок.</param>
		public void UnRegisterTrades(string securityId)
		{
			if (securityId.IsEmpty())
				throw new ArgumentNullException("securityId");

			if (!IsConnected)
				return;

			Do(server => server.CancelTicks(securityId));
		}

		/// <summary>
		/// Начать получать новую информацию по портфелю.
		/// </summary>
		/// <param name="portfolioName">Номер портфеля, по которому необходимо начать получать новую информацию.</param>
		public void RegisterPortfolio(string portfolioName)
		{
			if (portfolioName.IsEmpty())
				throw new ArgumentNullException("portfolioName");

			if (_registeredPortfolios.TryAdd(portfolioName))
				Do(server => server.ListenPortfolio(portfolioName));
		}

		/// <summary>
		/// Остановить получение новой информации по портфелю.
		/// </summary>
		/// <param name="portfolioName">Номер портфеля, по которому необходимо остановить получение новой информации.</param>
		public void UnRegisterPortfolio(string portfolioName)
		{
			if (portfolioName.IsEmpty())
				throw new ArgumentNullException("portfolioName");

			_registeredPortfolios.Remove(portfolioName);

			if (!IsConnected)
				return;

			Do(server => server.CancelPortfolio(portfolioName));
		}

		/// <summary>
		/// Начать получать исторические тиковые сделки от сервера SmartCOM через событие <see cref="NewHistoryTrade"/>.
		/// </summary>
		/// <param name="securityId">Идентификатор инструмента, для которого необходимо начать получать исторические сделки.</param>
		/// <param name="from">Временная точка отсчета.</param>
		/// <param name="count">Количество сделок.</param>
		public void RegisterHistoryTrades(string securityId, DateTime from, int count)
		{
			if (securityId.IsEmpty())
				throw new ArgumentNullException("securityId");

			Do(server => server.GetTrades(securityId, from, count));
		}

		/// <summary>
		/// Начать получать исторические временные свечки от сервера SmartCOM через событие <see cref="NewBar"/>.
		/// </summary>
		/// <param name="securityId">Идентификатор инструмента, для которого необходимо начать получать исторические свечки.</param>
		/// <param name="timeFrame">Тайм-фрейм.</param>
		/// <param name="from">Временная точка отсчета.</param>
		/// <param name="count">Количество свечек.</param>
		public void RegisterHistoryBars(string securityId, SmartTimeFrames timeFrame, DateTime from, int count)
		{
			if (securityId.IsEmpty())
				throw new ArgumentNullException("securityId");

			Do(server => server.GetBars(securityId, timeFrame.Interval, from, count));
		}

		private void FreeSmartCom()
		{
			_registeredPortfolios.Clear();

			CloseCheckAliveTimer();

			Do(server =>
			{
				UnSubscribeEvents();
				server.ReleaseComObject();
				_server = null;
			}, false);
		}

		private void Do(Action<StServer> action, bool requireCom = true)
		{
			if (action == null)
				throw new ArgumentNullException("action");

			_eventDispatcher.Add(() =>
			{
				if (_server == null)
				{
					if (requireCom)
						throw new InvalidOperationException("Подключение к SmartCOM не инициализировано.");
					else
						return;
				}

				action(_server);

				_lastTransactionTime = DateTime.Now;
			});
		}

		private void SubscribeEvents()
		{
			_server.AddBar += OnAddBar;
			_server.AddSymbol += OnAddSecurity;
			_server.AddTick += OnAddTrade;
			_server.AddTickHistory += OnAddTradeHistory;
			_server.AddTrade += OnAddMyTrade;
			_server.AddPortfolio += OnAddPortfolio;
			_server.UpdateBidAsk += OnUpdateQuotes;
			_server.UpdateOrder += OnUpdateOrder;
			_server.UpdatePosition += OnUpdatePosition;
			_server.UpdateQuote += OnUpdateSecurity;
			_server.OrderCancelFailed += OnOrderCancelFailed;
			_server.OrderCancelSucceeded += OnOrderCancelSucceeded;
			_server.OrderMoveFailed += OnOrderMoveFailed;
			_server.OrderMoveSucceeded += OnOrderMoveSucceeded;
			_server.OrderFailed += OnOrderFailed;
			_server.OrderSucceeded += OnOrderSucceded;
			_server.SetPortfolio += OnUpdatePortfolio;
			_server.Connected += OnConnected;
			_server.Disconnected += OnDisconnected;
		}

		private void UnSubscribeEvents()
		{
			_server.AddBar -= OnAddBar;
			_server.AddSymbol -= OnAddSecurity;
			_server.AddTick -= OnAddTrade;
			_server.AddTickHistory -= OnAddTradeHistory;
			_server.AddTrade -= OnAddMyTrade;
			_server.AddPortfolio -= OnAddPortfolio;
			_server.UpdateBidAsk -= OnUpdateQuotes;
			_server.UpdateOrder -= OnUpdateOrder;
			_server.UpdatePosition -= OnUpdatePosition;
			_server.UpdateQuote -= OnUpdateSecurity;
			_server.OrderCancelFailed -= OnOrderCancelFailed;
			_server.OrderCancelSucceeded -= OnOrderCancelSucceeded;
			_server.OrderMoveFailed -= OnOrderMoveFailed;
			_server.OrderMoveSucceeded -= OnOrderMoveSucceeded;
			_server.OrderFailed -= OnOrderFailed;
			_server.OrderSucceeded -= OnOrderSucceded;
			_server.SetPortfolio -= OnUpdatePortfolio;
			_server.Connected -= OnConnected;
			_server.Disconnected -= OnDisconnected;
		}

		private const string _portfolioSyncToken = "portfolio";
		private const string _tradeSyncToken = "trade";
		private const string _orderSyncToken = "order";
		private const string _securitySyncToken = "order";
		private const string _connectionSyncToken = "connection";
		private const string _quoteSyncToken = "quote";

		private void Async(Action action, string syncToken)
		{
			if (action == null)
				throw new ArgumentNullException("action");

			_eventDispatcher.Add(() => _process(action), syncToken);
		}

		private static decimal SafeCast(double value)
		{
			return value.IsNaN() ? 0 : (decimal)value;
		}

		private void OnAddSecurity(int row, int rowCount, string securityId, string shortName, string longName, string type, int decimals, int lotSize,
			double minStepPrice, double minStepSize, string isin, string board, DateTime expiryDate, double daysBeforeExpiry, double strike)
		{
			Async(() =>
				NewSecurity.SafeInvoke(row, rowCount, securityId, shortName, longName, type, decimals, lotSize, SafeCast(minStepPrice), SafeCast(minStepSize),
					isin, board, DateTime.FromOADate(0) == expiryDate ? (DateTime?)null : expiryDate, SafeCast(daysBeforeExpiry), SafeCast(strike)), _securitySyncToken);
		}

		private void OnUpdateSecurity(string securityId, DateTime time, double open, double high, double low, double close, double lastTradePrice, double volume,
			double lastTradeVolume, double bid, double ask, double bidSize, double askSize, double openInt, double goBuy, double goSell, double goBase,
			double goBaseBacked, double highLimit, double lowLimit, int tradingStatus, double volat, double theorPrice)
		{
			Async(() => SecurityChanged.SafeInvoke(securityId,
				new Trade
				{
					Time = time,
					Price = SafeCast(lastTradePrice),
					Volume = SafeCast(lastTradeVolume)
				},
				SafeCast(open), SafeCast(high), SafeCast(low), SafeCast(close), SafeCast(volume),
				new Quote
				{
					Price = SafeCast(bid),
					Volume = SafeCast(bidSize)
				}, new Quote
				{
					Price = SafeCast(ask),
					Volume = SafeCast(askSize)
				},
				SafeCast(openInt),
				new Tuple<decimal, decimal>(SafeCast(goBuy), SafeCast(goSell)),
				new Tuple<decimal, decimal>(SafeCast(goBase), SafeCast(goBaseBacked)),
				new Tuple<decimal, decimal>(SafeCast(lowLimit), SafeCast(highLimit)),
				tradingStatus, new Tuple<decimal, decimal>(SafeCast(volat), SafeCast(theorPrice))), _securitySyncToken);
		}

		private void OnUpdateQuotes(string securityId, int row, int rowCount, double bidPrice, double bidVolume, double askPrice, double askVolume)
		{
			Async(() => QuoteChanged.SafeInvoke(securityId, row, rowCount, SafeCast(bidPrice), SafeCast(bidVolume), SafeCast(askPrice), SafeCast(askVolume)), _quoteSyncToken);
		}

		private void OnAddMyTrade(string portfolio, string securityId, string orderId, double price, double amount, DateTime time, string tradeNo)
		{
			Async(() => NewMyTrade.SafeInvoke(portfolio, securityId, orderId.To<long>(), (decimal)price, SafeCast(amount.Abs()), time, tradeNo.To<long>()), _orderSyncToken);
		}

		private void OnAddTrade(string securityId, DateTime time, double price, double volume, string tradeNo, StOrder_Action action)
		{
			Async(() => NewTrade.SafeInvoke(securityId, time, (decimal)price, SafeCast(volume), tradeNo.To<long>(), action), _tradeSyncToken);
		}

		private void OnAddPortfolio(int row, int rowCount, string name, string exchange, StPortfolioStatus status)
		{
			Async(() => NewPortfolio.SafeInvoke(row, rowCount, name, exchange, status), _portfolioSyncToken);
		}

		private void OnUpdatePortfolio(string name, double cash, double leverage, double commission, double saldo)
		{
			Async(() => PortfolioChanged.SafeInvoke(name, (decimal)cash, (decimal)leverage, (decimal)commission, (decimal)saldo), _portfolioSyncToken);
		}

		private void OnUpdatePosition(string portfolioName, string securityId, double avPrice, double amount, double planned)
		{
			Async(() => PositionChanged.SafeInvoke(portfolioName, securityId, (decimal)avPrice, SafeCast(amount), SafeCast(planned)), _portfolioSyncToken);
		}

		private void OnAddTradeHistory(int row, int rowCount, string securityId, DateTime time, double price, double volume, string tradeno, StOrder_Action action)
		{
			Async(() => NewHistoryTrade.SafeInvoke(row, rowCount, securityId, time, (decimal)price, SafeCast(volume), tradeno.To<long>(), action), _tradeSyncToken);
		}

		private void OnAddBar(int row, int rowCount, string securityId, StBarInterval interval, DateTime time, double open, double high, double low, double close, double volume, double openInt)
		{
			Async(() => NewBar.SafeInvoke(row, rowCount, securityId, SmartTimeFrames.GetTimeFrame(interval), time, (decimal)open, (decimal)high, (decimal)low, (decimal)close, (decimal)volume, (decimal)openInt), _tradeSyncToken);
		}

		private void OnOrderSucceded(int cookie, string smartOrderId)
		{
			Async(() => NewOrder.SafeInvoke(cookie, smartOrderId), _orderSyncToken);
		}

		private void OnUpdateOrder(string portfolio, string securityId, StOrder_State state, StOrder_Action action, StOrder_Type type,
			StOrder_Validity validity, double price, double volume, double stop, double balance, DateTime time, string smartOrderId,
			string orderIdStr, int status, int transactionId)
		{
			// http://www.itinvest.ru/forum/index.php?showtopic=63063&st=0&p=242023&#entry242023
			Async(() => OrderChanged.SafeInvoke(portfolio, securityId, state,
					action, type, validity == StOrder_Validity.StOrder_Validity_Day, SafeCast(price), (int)volume,
					SafeCast(stop), (int)balance, time, smartOrderId, orderIdStr.To<long>(), status, transactionId), _orderSyncToken);
		}

		private void OnOrderFailed(int cookie, string smartOrderId, string reason)
		{
			Async(() => OrderFailed.SafeInvoke(cookie, smartOrderId, reason), _orderSyncToken);
		}

		private void OnOrderMoveSucceeded(string smartOrderId)
		{
			Async(() => OrderReRegistered.SafeInvoke(smartOrderId), _orderSyncToken);
		}

		private void OnOrderMoveFailed(string smartOrderId)
		{
			Async(() => OrderReRegisterFailed.SafeInvoke(smartOrderId), _orderSyncToken);
		}

		private void OnOrderCancelSucceeded(string smartOrderId)
		{
			Async(() => OrderCancelled.SafeInvoke(smartOrderId), _orderSyncToken);
		}

		private void OnOrderCancelFailed(string smartOrderId)
		{
			Async(() => OrderCancelFailed.SafeInvoke(smartOrderId), _orderSyncToken);
		}

		private void OnConnected()
		{
			Async(() =>
			{
				var interval = TimeSpan.FromSeconds(10);
				_checkAliveTimer = ThreadingHelper.Timer(() =>
				{
					if (_server == null)
						return;

					try
					{
						if ((DateTime.Now - _lastTransactionTime) > interval)
						{
							// ping
							Do(server => server.GetPrortfolioList(), false);
						}
					}
					catch (Exception ex)
					{
						CloseCheckAliveTimer();
						RaiseDisconnected(new InvalidOperationException("SmartCOM не ответил на проверку живой-мертвый. Возможно, приложение SmartCOM зависло.", ex));
					}
				})
				.Interval(interval);

				IsConnected = true;

				_trying = Trying.None;

				Connected.SafeInvoke();
			}, _connectionSyncToken);
		}

		private void OnDisconnected(string description)
		{
			Async(() =>
			{
				FreeSmartCom();

				if (description.IsEmpty())
				{
					switch (_trying)
					{
						case Trying.None:
							description = "Неизвестная ошибка.";
							break;
						case Trying.Connect:
							description = "Неудалось произвести подключение.";
							break;
						case Trying.Disconnect:
							description = string.Empty;
							break;
						default:
							throw new ArgumentOutOfRangeException();
					}
				}
				else
				{
					switch (description.Trim())
					{
						case "Bad user name or password":
							description = "Неправильный логин или пароль.";
							break;

						case "disconnected by user.":
							description = string.Empty;
							break;

						case "Timout detected. check your  internet connectivity or event handler code":
							description = "Время установления связи со SmartCOM вышло.";
							break;

						case "WINSOCK":
							description = "Сетевая ошибка.";
							break;
					}
				}

				_trying = Trying.None;

				RaiseDisconnected(description);
			}, _connectionSyncToken);
		}

		private void RaiseDisconnected(string errorDescription)
		{
			RaiseDisconnected(errorDescription.IsEmpty() ? null : new InvalidOperationException(errorDescription));
		}

		private void RaiseDisconnected(Exception error)
		{
			_errorWhileDisconnect = error != null;
			Disconnected.SafeInvoke(error);
		}

		private void CloseCheckAliveTimer()
		{
			IsConnected = false;

			if (_checkAliveTimer != null)
			{
				_checkAliveTimer.Dispose();
				_checkAliveTimer = null;
			}
		}

		//private bool IsExportRunning { get; set; }

		//private void ThrowIfNotConnected()
		//{
		//    if (_server == null || !IsConnected)
		//        throw new InvalidOperationException("Не было произведено подключение к SmartCOM.");
		//}

		/// <summary>
		/// Освободить занятые ресурсы. В частности, отключиться от сервера SmartCOM.
		/// </summary>
		protected override void DisposeManaged()
		{
			Disconnect();

			_eventDispatcher.Dispose();

			base.DisposeManaged();
		}
	}
}